/**
 * Copyright 2006-2015 handu.com
 * <p/>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ouwen.smartpay.service.dubbo;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.utils.NetUtils;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.registry.RegistryService;
import com.alibaba.dubbo.rpc.service.GenericService;
import com.fm.common.annotation.OpenApiMethod;
import com.fm.common.annotation.OpenApiService;
import com.fm.core.utils.Collections3;
import com.fm.core.utils.Strings;
import com.google.inject.internal.util.Sets;
import com.ouwen.smartpay.domain.MetaDataWarpper;
import com.ouwen.smartpay.domain.ProviderWarpper;
import com.ouwen.smartpay.domain.ServiceWarpper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.ClassUtils;

import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/***
 *@author <a href="http://youngitman.tech">青年IT男</a>
 *@version v1.0.0
 *@className RegistryContainer
 *@description 注册中心服务监听，只关注在暴露服务接口标注注解{@link OpenApiService}的服务
 *@JunitTest: {@link  }
 *@date 22:54 2020-04-22
 *
 **/
@Service
public class RegistryContainer implements InitializingBean, DisposableBean {

    Logger logger = LoggerFactory.getLogger(getClass());

    /*** 提供者信息的元数据包装 key = ${interfaceClass} ,value=[]*/
    private Map<String, Set<MetaDataWarpper>> providerMetaDataMaps = new ConcurrentHashMap<>(256);

    /***服务名(服务码)查找提供者服务信息。例如：key=服务码,value=map{key = 服务版本+":"+分组 (必须唯一) => value = ProviderWarpper } 提供者信息包装到方法级别*/
    private final Map<String, Map<String, ProviderWarpper>> serviceNameToProviderWarpper = new ConcurrentHashMap<>(256);

    /*** 默认提供者信息包装，表示没有查找到服务提供者信息 */
    private final ProviderWarpper defalutProviderWarpper = ProviderWarpper.builder().build();

    private final MetaDataWarpper defalutMetaDataWarpper = MetaDataWarpper.builder().build();

    @Reference
    private RegistryService registry;

    @Autowired
    private RegistryConfig registryConfig;

    @Autowired
    private ApplicationConfig applicationConfig;

    private Object lock = new Object();

    public MetaDataWarpper getDefalutMetaDataWarpper() {

        return defalutMetaDataWarpper;
    }


    public ProviderWarpper getDefalutProviderWarpper() {
        return defalutProviderWarpper;
    }

    @PostConstruct
    public void init() {

   /*     new Thread(() -> {
            while (true) {
                System.out.println("开始打印提供者信息");
                try {
                    serviceNameToProviderWarpper.forEach((k,v)->{
                        v.forEach((k1,v1) -> System.out.println("存在服务码："+k+"：Key=" + k1 + ",Value=" + v1));
                    });

                    providerMetaDataMaps.forEach((k, v) -> {
                        v.parallelStream().forEach(_v -> System.out.println("存在提供者信息：Key=" + k + ",Value=" + _v));

                    });
                    System.out.println("结束打印提供者信息");

                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();*/

    }


    public MetaDataWarpper findMetaDataWarpper(ServiceWarpper serviceWarpper) {

        Set<MetaDataWarpper> metaDataWarppers = providerMetaDataMaps.get(serviceWarpper.getInterfaceName());

        if (Collections3.isEmpty(metaDataWarppers)) {

            return defalutMetaDataWarpper;
        }

        List<MetaDataWarpper> _metaDataWarppers = metaDataWarppers.parallelStream()
                .filter(metaDataWarpper -> Strings.equals(metaDataWarpper.getGroup(), serviceWarpper.getGroup())
                        && Strings.equals(metaDataWarpper.getVersion(), serviceWarpper.getVersion())
                        && Strings.equals(metaDataWarpper.getInterfaceClass().getName(), serviceWarpper.getInterfaceName()))
                .collect(Collectors.toList());

        if (Collections3.isEmpty(_metaDataWarppers)) {

            return defalutMetaDataWarpper;
        }

        return _metaDataWarppers.get(0);
    }

    /***
     *
     * 通过服务名、分组、版本号查找服务注册信息
     *
     * @author liyong
     * @date 13:45 2020-04-22
     * @param serviceWarpper
     * @exception
     * @return ProviderWarpper
     **/
    public ProviderWarpper findServiceInterfaceClassPath(ServiceWarpper serviceWarpper) {

        Map<String, ProviderWarpper> providerWarpperMap = serviceNameToProviderWarpper.get(serviceWarpper.getServiceName());

        if (providerWarpperMap == null || providerWarpperMap.size() <= 0) {

            return defalutProviderWarpper;
        }

        ProviderWarpper providerWarpper = providerWarpperMap.get(buildVersionGroupKey(serviceWarpper.getVersion(), serviceWarpper.getGroup()));

        if (providerWarpper == null) {

            return defalutProviderWarpper;
        }

        return providerWarpper;
    }


    /***
     *@author <a href="http://youngitman.tech">青年IT男</a>
     *@version v1.0.0
     *@className RegistryContainer
     *@description ReferenceConfig缓存key, 不需缓存到方法级别
     *@JunitTest: {@link  }
     *@date 00:05 2020-04-23
     *
     **/
    private String interfaceNameToKey(String interfaceClass) {
        StringBuilder sb = new StringBuilder();
        sb.append(interfaceClass);
        return sb.toString();
    }

    @Override
    public void afterPropertiesSet() {

        URL subscribeUrl = new URL(Constants.ADMIN_PROTOCOL, NetUtils.getLocalHost(), 0, "",
                Constants.INTERFACE_KEY, Constants.ANY_VALUE,
                Constants.GROUP_KEY, Constants.ANY_VALUE,
                Constants.VERSION_KEY, Constants.ANY_VALUE,
                Constants.CLASSIFIER_KEY, Constants.ANY_VALUE,
                //只订阅提供者节点变化
                Constants.CATEGORY_KEY, Constants.PROVIDERS_CATEGORY,
                //+订阅消费者节点变化 ","+ Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false));

        //订阅zookeeper节点
        registry.subscribe(subscribeUrl, urls -> {

            if (urls == null || urls.size() == 0) {
                return;
            }

            Set<URL> providers = Sets.newHashSet();

            Set<String> downs = Sets.newHashSet();


            for (URL url : urls) {

                String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);

                //服务提供者发生变化
                if (Constants.PROVIDERS_CATEGORY.equals(category)) {

                    //服务接口全限定名称
                    String serviceInterface = url.getServiceInterface();

                    //通知empty://表示没有提供者，提供者已经下线完
                    if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) {

                        downs.add(serviceInterface);

                    } else {

                        //通知回来注册中心还有多少提供者信息（把存在的提供者信息通知回来）
                        providers.add(url);
                    }
                }
            }

            //统一时刻只能处理一个上线/下线处理
            if (Collections3.isNotEmpty(downs)) {

                synchronized (lock) {

                    //服务全部下线
                    serviceDown(downs);
                }
            }

            if (Collections3.isNotEmpty(providers)) {

                synchronized (lock) {

                    //存在服务变更
                    serviceChange(providers);
                }
            }

        });

    }

    private void removeServiceNameToProviderWarpper(Set<MetaDataWarpper> removes) {

        //清除serviceName对应关系
        if (Collections3.isNotEmpty(removes)) {
            removes.parallelStream()
                    .map(metaDataWarpper -> metaDataWarpper.getProviderWarppers())
                    .flatMap(providerWarppers -> providerWarppers.stream())
                    .map(providerWarpper -> providerWarpper.getServiceName())
                    .forEach(serviceName -> serviceNameToProviderWarpper.remove(serviceName));
        }
    }

    /***
     *
     * 服务变更
     *
     * @author liyong
     * @date 23:06 2020-04-22
     * @param providers 注册中心存在的提供者信息列表
     * @exception
     * @return void
     **/
    private void serviceChange(Set<URL> providers) {

        providers.stream().forEach(url -> {

            String serviceInterface = url.getParameter(Constants.INTERFACE_KEY);

            String classCachekey = interfaceNameToKey(serviceInterface);

            Set<MetaDataWarpper> removes = providerMetaDataMaps.remove(classCachekey);

            //清除serviceName对应关系
            removeServiceNameToProviderWarpper(removes);

        });

        providers.stream().forEach(url -> {

            String application = url.getParameter(Constants.APPLICATION_KEY);

            String serviceInterface = url.getParameter(Constants.INTERFACE_KEY);

            if (application != null && application.length() > 0) {

                MetaDataWarpper metaDataWarpper = buildMetaDataWarpper(url);

                if (defalutMetaDataWarpper != metaDataWarpper) {

                    String classCachekey = interfaceNameToKey(serviceInterface);

                    providerMetaDataMaps.computeIfAbsent(classCachekey, key -> Sets.newHashSet());

                    Set<MetaDataWarpper> metaDataWarppers = providerMetaDataMaps.get(classCachekey);

                    //Set存在数据不会被覆盖
                    metaDataWarppers.remove(metaDataWarpper);

                    metaDataWarppers.add(metaDataWarpper);

                    providerMetaDataMaps.put(serviceInterface, metaDataWarppers);

                }
            }
        });
    }

    /***
     *
     * 构建MetaDataWarpper
     *
     * @author liyong
     * @date 00:00 2020-04-24
     * @param url
     * @exception
     * @return com.fbank.smartpay.domain.MetaDataWarpper
     **/
    private MetaDataWarpper buildMetaDataWarpper(URL url) {

        //服务接口全限定名称
        String serviceInterface = url.getServiceInterface();

        String application = url.getParameter(Constants.APPLICATION_KEY);

        String version = url.getParameter(Constants.VERSION_KEY);

        String group = Strings.isEmpty(url.getParameter(Constants.GROUP_KEY)) ? "" : url.getParameter(Constants.GROUP_KEY);

        MetaDataWarpper.MetaDataWarpperBuilder builder = MetaDataWarpper.builder();

        try {

            ClassLoader classLoader = ClassUtils.getDefaultClassLoader();

            if(Strings.isEmpty(serviceInterface)){

                return defalutMetaDataWarpper;

            }

            if(!serviceInterface.contains("com.fbank.smartpay")){

                return defalutMetaDataWarpper;

            }

            AtomicReference<Class> _interface = new AtomicReference<>();

            try {

                _interface.set(classLoader.loadClass(serviceInterface));

            }catch (Exception e){}

            if(_interface.get() == null){

                return defalutMetaDataWarpper;

            }

            //必须要在类似标注
            OpenApiService openApiService = AnnotationUtils.findAnnotation(_interface.get(), OpenApiService.class);

            if (openApiService == null) {

                return defalutMetaDataWarpper;
            }

            //添加class
            builder.interfaceClass(_interface.get());

            //服务分组
            builder.group(group);

            //服务版本
            builder.version(version);

            //获取暴露方法
            List<Method> methods = Arrays.stream(_interface.get().getDeclaredMethods())
                    .filter(method -> method.getDeclaredAnnotation(OpenApiMethod.class) != null)
                    .filter(method -> Strings.isNotBlank(method.getDeclaredAnnotation(OpenApiMethod.class).service()))
                    .collect(Collectors.toList());

            if (Collections3.isEmpty(methods)) {

                return defalutMetaDataWarpper;
            }

            Set<String> _methods = methods.parallelStream().map(method -> method.getName()).collect(Collectors.toSet());

            builder.methods(_methods);

            //构建ReferenceConfig
            ReferenceConfig<GenericService> reference = new ReferenceConfig<>();

            ApplicationConfig applicationConfig = new ApplicationConfig();

            applicationConfig.setName(application);

            reference.setApplication(applicationConfig);

            reference.setRegistry(registryConfig);
            // 接口名
            reference.setInterface(_interface.get());
            // 声明为泛化接口
            reference.setGeneric(true);
            //开启异步模式
            //reference.setAsync(true);
            //版本
            reference.setVersion(version);
            //分组
            reference.setGroup(group);

            builder.referenceConfig(reference);

            //转换为ProviderWarpper
            Set<ProviderWarpper> providerWarppers = methods.stream().map(m -> {

                Parameter parameter = m.getParameters()[0];

                //获取方法参数
                Class<?> orderType = parameter.getType();

                //返回的类型
                Class<?> returnType = m.getReturnType();


                //服务码
                OpenApiMethod openApiMethod = AnnotationUtils.findAnnotation(m, OpenApiMethod.class);

                String serviceName = openApiMethod.service();

                //构建ProviderWarpper
                ProviderWarpper providerWarpper = ProviderWarpper.builder()
                        .group(group)
                        .interfaceClass(_interface.get())
                        .method(m)
                        .version(version)
                        .order(orderType)
                        .serviceName(serviceName)
                        .application(application)
                        .result(returnType).build();

                //设置服务码对应的ProviderWarpper
                serviceNameToProviderWarpper.computeIfAbsent(serviceName, (key) -> new HashMap<>());

                Map<String, ProviderWarpper> serviceToProviderWarpper = serviceNameToProviderWarpper.get(serviceName);

                serviceToProviderWarpper.put(buildVersionGroupKey(version, group), providerWarpper);

                return providerWarpper;

            }).collect(Collectors.toSet());

            builder.providerWarppers(providerWarppers);


        } catch (Exception e) {
            logger.error("构建服务提供者元信息异常:", e);
        }

        return builder.build();
    }

    private String buildVersionGroupKey(String version, String group) {
        return version + ":" + group;
    }

    /***
     *
     * 服务下线,其所有接口失效(Dubbo有个小bug某个版本的服务下线并不通知只会推送存量的接口服务列表，某个接口全部下线会发送下线通知)，
     * 当存在多个会存在问题
     *
     * @author liyong
     * @date 23:06 2020-04-22
     * @param downs
     * @exception
     * @return void
     **/
    private void serviceDown(Set<String> downs) {

        downs.parallelStream().forEach(serviceInterface -> {

            String classCachekey = interfaceNameToKey(serviceInterface);

            //接口所有服务下线
            Set<MetaDataWarpper> removes = providerMetaDataMaps.remove(classCachekey);

            //清除serviceName对应关系
            removeServiceNameToProviderWarpper(removes);

        });
    }

    @Override
    public void destroy() {

        serviceNameToProviderWarpper.clear();

        providerMetaDataMaps.clear();
    }
}